package com.luorrak.ouroboros.api; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.graphics.Color; import android.graphics.Typeface; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.BackgroundColorSpan; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; import android.view.View; import com.luorrak.ouroboros.R; import com.luorrak.ouroboros.thread.CardDialogFragment; import com.luorrak.ouroboros.thread.ExternalNavigationWarningFragment; import com.luorrak.ouroboros.thread.InterThreadNavigationWarningFragment; import com.luorrak.ouroboros.util.InfiniteDbHelper; import com.luorrak.ouroboros.util.SpoilerSpan; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; /** * Ouroboros - An 8chan browser * Copyright (C) 2015 Luorrak * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ public class CommentParser { public final static int CATALOG_VIEW = 0; public final static int THREAD_VIEW = 1; public Spannable parseId(String id){ SpannableString coloredId = new SpannableString(id); coloredId.setSpan(new ForegroundColorSpan(Color.parseColor("#" + id)), 0, coloredId.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return coloredId; } /* Example formatting <p class="body-line ltr ">normal text</p> <p class="body-line ltr "><span class="heading">Red Text</span></p> <p class="body-line ltr quote">>Green Text</p> <p class="body-line ltr "><span class="spoiler">Spoiler Text</span></p> <p class="body-line ltr "><em>Italic Text</em></p> <p class="body-line ltr "><strong>Bold Text</strong></p> <p class="body-line ltr "><u>Under Line</u></p> <p class="body-line ltr "><s>Strike-through</s></p> <p class="body-line ltr "><span class="aa">Escaped text</span></p> <p class="body-line ltr "> <code> <pre class='prettyprint' style='display:inline-block'>codeblock</pre> </code> </p> */ /* JSoup making it this for some reason <html> <head></head> <body> <p class="body-line ltr ">normal text</p> <p class="body-line ltr "><span class="heading">Red Text</span></p> <p class="body-line ltr quote">>Green Text</p> <p class="body-line ltr "><span class="spoiler">Spoiler Text</span></p> <p class="body-line ltr "><em>Italic Text</em></p> <p class="body-line ltr "><strong>Bold Text</strong></p> <p class="body-line ltr "><u>Under Line</u></p> <p class="body-line ltr "><s>Strike-through</s></p> <p class="body-line ltr "><span class="aa">Escaped text</span></p> <p class="body-line ltr "><code></code></p> <pre class="prettyprint" style="display:inline-block"><code>codeblock</code></pre> <p></p> </body> </html> <p class="body-line empty "> <p class=\"body-line ltr \"><a onclick=\"highlightReply('22543', event);\" href=\"\/test\/res\/22543.html#22543\">>>22543<\/a><\/p> <p class=\"body-line ltr \"><a href=\"\/irc\/res\/468.html#468\">>>>\/irc\/468<\/a><\/p> <p class=\"body-line ltr \"><a href=\"https:\/\/www.ixquick.com\/\" rel=\"nofollow\" target=\"_blank\">https:\/\/www.ixquick.com\/<\/a><\/p> */ public Spannable parseCom(String rawCom, int viewState, String currentBoard, String resto, FragmentManager fragmentManager, InfiniteDbHelper infiniteDbHelper){ CharSequence processedText = new SpannableString(""); Document doc = Jsoup.parse(rawCom); int parseLimit = 4; int limit = 0; if (doc.select("p").size() == 0) { return new SpannableString("LEGACY COMMENT SYSTEM!\n " + doc.body().text()); } else { for (Element bodyLine : doc.body().children()){ //This speeds up swiping on catalogview without risking an error if (viewState == CATALOG_VIEW && limit == parseLimit){ break; } limit++; if (bodyLine.className().equals("body-line ltr quote")){ processedText = TextUtils.concat(processedText, parseGreenText(new SpannableString(parseFormatting(bodyLine, currentBoard, resto, fragmentManager, infiniteDbHelper)))); processedText = TextUtils.concat(processedText, "\n"); } else if (bodyLine.className().equals("body-line ltr")){ if (bodyLine.children().size() == 0){ //Normal Text processedText = TextUtils.concat(processedText, parseNormalText(new SpannableString(bodyLine.text()))); } else { processedText = TextUtils.concat(processedText, parseFormatting(bodyLine, currentBoard, resto, fragmentManager, infiniteDbHelper)); } processedText = TextUtils.concat(processedText, "\n"); } else if (bodyLine.className().equals("body-line empty")){ processedText = TextUtils.concat(processedText, "\n"); } else if (bodyLine.tagName().equals("pre")){ processedText = TextUtils.concat(processedText, parseCodeText(bodyLine)); } } } //trim trailing newline. if (processedText.length() > 0 ){ processedText = processedText.subSequence(0, processedText.length() - 1); } return SpannableStringBuilder.valueOf(processedText); } //nested switch statement private CharSequence parseSpanText(Element child){ CharSequence spanText = new SpannableString(""); switch (child.className()){ case "heading": spanText = parseHeadingText(new SpannableString(child.text())); break; case "spoiler": spanText = parseSpoilerText(new SpannableString(child.text())); break; case "aa": spanText = parseEscapedText(new SpannableString(child.text())); break; case "tex": spanText = parseTexText(new SpannableString(child.text())); break; } return spanText; } private CharSequence parseFormatting(Element bodyLine, String currentBoard, String resto, FragmentManager fragmentManager, InfiniteDbHelper infiniteDbHelper){ CharSequence parsedText = ""; for (Node childNode : bodyLine.childNodes()){ if (childNode instanceof TextNode){ parsedText = TextUtils.concat(parsedText, parseNormalText(new SpannableString(((TextNode) childNode).text()))); } else if (childNode instanceof Element){ Element childElement = (Element) childNode; switch(childElement.tagName()){ default: parsedText = TextUtils.concat(parsedText, parseNormalText(new SpannableString(childElement.text()))); break; case "span": CharSequence spanText = parseSpanText(childElement); parsedText = TextUtils.concat(parsedText, spanText); break; case "em": parsedText = TextUtils.concat(parsedText, parseItalicText(new SpannableString(childElement.text()))); break; case "strong": parsedText = TextUtils.concat(parsedText, parseBoldText(new SpannableString(childElement.text()))); break; case "u": parsedText = TextUtils.concat(parsedText, parseUnderlineText(new SpannableString(childElement.text()))); break; case "s": parsedText = TextUtils.concat(parsedText, parseStrikethroughText(new SpannableString(childElement.text()))); break; case "a": parsedText = TextUtils.concat(parsedText, parseAnchorText(childElement, currentBoard, resto, fragmentManager, infiniteDbHelper)); } } } return parsedText; } private CharSequence parseNormalText(SpannableString normalText){ return normalText; } private CharSequence parseGreenText(SpannableString greenText){ greenText.setSpan(new ForegroundColorSpan(Color.parseColor("#789922")), 0, greenText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return greenText; } private CharSequence parseSpoilerText(SpannableString spoilerText){ SpoilerSpan spoilerSpan = new SpoilerSpan(); spoilerText.setSpan(spoilerSpan, 0, spoilerText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return spoilerText; } private CharSequence parseItalicText(SpannableString italic){ italic.setSpan(new StyleSpan(Typeface.ITALIC), 0, italic.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return italic; } private CharSequence parseBoldText(SpannableString bold){ bold.setSpan(new StyleSpan(Typeface.BOLD), 0, bold.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return bold; } private CharSequence parseUnderlineText(SpannableString underline) { underline.setSpan(new UnderlineSpan(), 0, underline.length(), 0); return underline; } private CharSequence parseHeadingText(SpannableString heading){ heading.setSpan(new ForegroundColorSpan(Color.RED), 0, heading.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); heading.setSpan(new StyleSpan(Typeface.BOLD), 0, heading.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return heading; } private CharSequence parseStrikethroughText(SpannableString strikethrough){ strikethrough.setSpan(new StrikethroughSpan(), 0, strikethrough.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return strikethrough; } private CharSequence parseEscapedText(SpannableString escapedText){ return escapedText; } private CharSequence parseTexText(SpannableString escapedText){ return escapedText; } private CharSequence parseCodeText(Element codeElement){ Element preElement = codeElement.child(0); SpannableString codeText = new SpannableString("\n" + preElement.text() + "\n"); codeText.setSpan(new BackgroundColorSpan(Color.LTGRAY), 0, codeText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return codeText; } private CharSequence parseAnchorText(final Element anchor, final String currentBoard, final String resto, final FragmentManager fragmentManager, InfiniteDbHelper infiniteDbHelper) { final String linkUrl = anchor.attr("href"); SpannableString linkText = new SpannableString(anchor.text()); if (linkUrl.contains("http")) { //normal link ClickableSpan clickableNormalLink = new ClickableSpan() { @Override public void onClick(View widget) { ExternalNavigationWarningFragment dialog = ExternalNavigationWarningFragment.newInstance(linkUrl); dialog.show(fragmentManager, "externallink"); } }; linkText.setSpan(new ForegroundColorSpan(Color.parseColor("#0645AD")), 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); linkText.setSpan(clickableNormalLink, 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return linkText; } else if (linkUrl.contains("_g")){ //normal link ClickableSpan clickableNormalLink = new ClickableSpan() { @Override public void onClick(View widget) { ExternalNavigationWarningFragment dialog = ExternalNavigationWarningFragment.newInstance(anchor.text()); dialog.show(fragmentManager, "externallink"); } }; linkText.setSpan(new ForegroundColorSpan(Color.parseColor("#0645AD")), 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); linkText.setSpan(clickableNormalLink, 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return linkText; } else if (linkUrl.contains(resto)){ //same thread if (infiniteDbHelper.isNoUserPost(currentBoard, linkUrl.split("#")[1])){ linkText = SpannableString.valueOf(TextUtils.concat(linkText, " (You)")); } else if (linkUrl.split("#")[1].equals(resto)){ linkText = SpannableString.valueOf(TextUtils.concat(linkText, " (OP)")); } ClickableSpan clickableSameThreadLink = new ClickableSpan() { @Override public void onClick(View widget) { FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); CardDialogFragment cardDialogFragment = CardDialogFragment.showPost(linkUrl, currentBoard); fragmentTransaction.add(R.id.placeholder_card, cardDialogFragment) .addToBackStack("threadDialog") .commit(); } }; linkText.setSpan(clickableSameThreadLink, 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); linkText.setSpan(new ForegroundColorSpan(Color.parseColor("#FF6600")), 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return linkText; } else { //different thread ClickableSpan clickableDifferentThreadLink = new ClickableSpan() { @Override public void onClick(View widget) { InterThreadNavigationWarningFragment dialog = InterThreadNavigationWarningFragment.newInstance(linkUrl); dialog.show(fragmentManager, "internallink"); } }; linkText.setSpan(clickableDifferentThreadLink, 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); linkText.setSpan(new ForegroundColorSpan(Color.parseColor("#FF6600")), 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return linkText; } } }